#region Copyright & License Information

/*
 * Copyright 2007-2018 The OpenRA Developers (see AUTHORS)
 * This file is part of OpenRA, which is free software. It is made
 * available to you under the terms of the GNU General Public License
 * as published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version. For more
 * information, see COPYING.
 */

#endregion

using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using OpenRA;
using OpenRA.Graphics;
using OpenRA.Mods.Common.Activities;
using OpenRA.Mods.Common.Effects;
using OpenRA.Mods.Common.Graphics;
using OpenRA.Mods.Common.Traits;
using OpenRA.Traits;

namespace OpenRa.Mods.MW.Traits
{
    class MwAttackOrderPowerInfo : SupportPowerInfo, Requires<AttackBaseInfo>
    {
        [Desc("Range of cells the camera should reveal around target cell.")]
        public readonly WDist CameraRange = WDist.Zero;

        [Desc("Can the camera reveal shroud generated by the GeneratesShroud trait?")]
        public readonly bool RevealGeneratedShroud = true;

        [Desc("Reveal cells to players with these stances only.")]
        public readonly Stance CameraStances = Stance.Ally;

        [Desc("Amount of time before detonation to spawn the camera.")]
        public readonly int CameraSpawnAdvance = 25;

        [Desc("Amount of time after detonation to remove the camera.")]
        public readonly int CameraRemoveDelay = 25;

        [Desc("Amount of time before detonation to remove the beacon.")]
        public readonly int BeaconRemoveAdvance = 25;

        public readonly WDist Speed = new WDist(89);

        public override object Create(ActorInitializer init)
        {
            return new MwAttackOrderPower(init.Self, this);
        }
    }

    class MwAttackOrderPower : SupportPower, INotifyCreated, INotifyBurstComplete, IResolveOrder
    {
        readonly MwAttackOrderPowerInfo info;
        AttackBase attack;

        public MwAttackOrderPower(Actor self, MwAttackOrderPowerInfo info)
            : base(self, info)
        {
            this.info = info;
        }

        public override void SelectTarget(Actor self, string order, SupportPowerManager manager)
        {
            Game.Sound.PlayToPlayer(SoundType.UI, manager.Self.Owner, Info.SelectTargetSound);
            self.World.OrderGenerator = new SelectAttackPowerTarget(self, order, manager, info.Cursor, MouseButton.Left, attack);
        }

        public override void Activate(Actor self, Order order, SupportPowerManager manager)
        {
            base.Activate(self, order, manager);
            attack.AttackTarget(Target.FromCell(self.World, order.TargetLocation), false, false, true);

            if (info.CameraRange != WDist.Zero)
            {
                var type = info.RevealGeneratedShroud
                    ? Shroud.SourceType.Visibility
                    : Shroud.SourceType.PassiveVisibility;

                self.World.AddFrameEndTask(w => w.Add(new RevealShroudEffect(self.World.Map.CenterOfCell(order.TargetLocation), info.CameraRange, type, self.Owner,
                    info.CameraStances, +(self.CenterPosition - self.World.Map.CenterOfCell(order.TargetLocation)).Length / info.Speed.Length - info.CameraSpawnAdvance,
                    info.CameraSpawnAdvance + info.CameraRemoveDelay)));
            }

            if (Info.DisplayBeacon)
            {
                var beacon = new Beacon(
                    self.Owner,
                    self.World.Map.CenterOfCell(order.TargetLocation),
                    Info.BeaconPaletteIsPlayerPalette,
                    Info.BeaconPalette,
                    Info.BeaconImage,
                    Info.BeaconPoster,
                    Info.BeaconPosterPalette,
                    Info.ArrowSequence,
                    Info.CircleSequence,
                    Info.ClockSequence,
                    null,
                    Info.BeaconDelay,
                    info.BeaconRemoveAdvance + info.CameraSpawnAdvance + (self.CenterPosition - self.World.Map.CenterOfCell(order.TargetLocation)).Length / info.Speed.Length);

                self.World.AddFrameEndTask(w => { w.Add(beacon); });
            }
        }

        protected override void Created(Actor self)
        {
            attack = self.Trait<AttackBase>();

            base.Created(self);
        }

        void INotifyBurstComplete.FiredBurst(Actor self, Target target, Armament a)
        {
            if (self.World.LocalPlayer != null && self.World.LocalPlayer == self.Owner)
            {
                self.World.IssueOrder(new Order("Stop", self, false));
            }
        }

        public void ResolveOrder(Actor self, Order order)
        {
            if (order.OrderString == "Stop")
                self.CancelActivity();
        }
    }

    public class SelectAttackPowerTarget : IOrderGenerator
    {
        readonly SupportPowerManager manager;
        readonly SupportPowerInstance instance;
        readonly string order;
        readonly string cursor;
        readonly string cursorBlocked;
        readonly MouseButton expectedButton;
        readonly AttackBase attack;

        public SelectAttackPowerTarget(Actor self, string order, SupportPowerManager manager, string cursor, MouseButton button, AttackBase attack)
        {
            // Clear selection if using Left-Click Orders
            if (Game.Settings.Game.UseClassicMouseStyle)
                manager.Self.World.Selection.Clear();

            instance = manager.GetPowersForActor(self).FirstOrDefault();
            this.manager = manager;
            this.order = order;
            this.cursor = cursor;
            expectedButton = button;
            this.attack = attack;
            cursorBlocked = cursor + "-blocked";
        }

        bool IsValidTarget(World world, CPos cell)
        {
            var pos = world.Map.CenterOfCell(cell);
            var range = attack.GetMaximumRange().LengthSquared;

            return world.Map.Contains(cell) && instance.Instances.Any(a => !a.IsTraitPaused && (a.Self.CenterPosition - pos).HorizontalLengthSquared < range);
        }

        IEnumerable<Order> IOrderGenerator.Order(World world, CPos cell, int2 worldPixel, MouseInput mi)
        {
            world.CancelInputMode();
            if (mi.Button == expectedButton && IsValidTarget(world, cell))
                yield return new Order(order, manager.Self, Target.FromCell(world, cell), false)
                {
                    SuppressVisualFeedback = true
                };
        }

        void IOrderGenerator.Tick(World world)
        {
            // Cancel the OG if we can't use the power
            if (!manager.Powers.ContainsKey(order))
                world.CancelInputMode();
        }

        IEnumerable<IRenderable> IOrderGenerator.Render(WorldRenderer wr, World world)
        {
            yield break;
        }

        IEnumerable<IRenderable> IOrderGenerator.RenderAboveShroud(WorldRenderer wr, World world)
        {
            foreach (var a in instance.Instances.Where(i => !i.IsTraitPaused))
            {
                yield return new RangeCircleRenderable(
                    a.Self.CenterPosition,
                    attack.GetMinimumRange(),
                    0,
                    Color.Red,
                    Color.FromArgb(96, Color.Black));

                yield return new RangeCircleRenderable(
                    a.Self.CenterPosition,
                    attack.GetMaximumRange(),
                    0,
                    Color.Red,
                    Color.FromArgb(96, Color.Black));
            }
        }

        string IOrderGenerator.GetCursor(World world, CPos cell, int2 worldPixel, MouseInput mi)
        {
            return IsValidTarget(world, cell) ? cursor : cursorBlocked;
        }
    }
}